﻿namespace WLFramework.Reflection
{
    using System;
    using System.Linq;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Collections.Generic;

	public delegate object CtorDelegate();
	public delegate object MethodDelegate(object target, object[] args);
	public delegate object GetValueDelegate(object target);
    public delegate void SetValueDelegate(object target, object arg);

	public static class DynamicDelegateFactory
    {
        #region Creatter
        public static CtorDelegate CreateConstructorDelegate(this ConstructorInfo constructor)
		{
            Shield.AssertNotNull(ref constructor, "constructor");
            Shield.AssertIsNotTrue(constructor.GetParameters().Length > 0, "不支持有参数的构造函数");

			DynamicMethod dm = new DynamicMethod(
				"ctor",
				constructor.DeclaringType,
				Type.EmptyTypes,
				true);

			ILGenerator il = dm.GetILGenerator();
			il.Emit(OpCodes.Nop);
			il.Emit(OpCodes.Newobj, constructor);
			il.Emit(OpCodes.Ret);

			return (CtorDelegate)dm.CreateDelegate(typeof(CtorDelegate));
		}
        public static TargetCreater CreateConstructor(this Type type)
        {
            return new TargetCreater()
            {
                Name = type.FullName,
                TargetType = type,
                Ctor = CreateConstructorDelegate(type.GetConstructor(Type.EmptyTypes))
            };
        }
        #endregion

        public static MethodDelegate CreateMethodDelegate(this MethodInfo method)
		{
			ParameterInfo[] pi = method.GetParameters();

			DynamicMethod dm = new DynamicMethod("DynamicMethod", typeof(object),
				new Type[] { typeof(object), typeof(object[]) },
				typeof(DynamicDelegateFactory), true);

			ILGenerator il = dm.GetILGenerator();

			il.Emit(OpCodes.Ldarg_0);

			for( int index = 0; index < pi.Length; index++ ) {
				il.Emit(OpCodes.Ldarg_1);
				il.Emit(OpCodes.Ldc_I4, index);

				Type parameterType = pi[index].ParameterType;
				if( parameterType.IsByRef ) {
					parameterType = parameterType.GetElementType();
					if( parameterType.IsValueType ) {
						il.Emit(OpCodes.Ldelem_Ref);
						il.Emit(OpCodes.Unbox, parameterType);
					}
					else {
						il.Emit(OpCodes.Ldelema, parameterType);
					}
				}
				else {
					il.Emit(OpCodes.Ldelem_Ref);

					if( parameterType.IsValueType ) {
						il.Emit(OpCodes.Unbox, parameterType);
						il.Emit(OpCodes.Ldobj, parameterType);
					}
				}
			}

			if( (method.IsAbstract || method.IsVirtual)
				&& !method.IsFinal && !method.DeclaringType.IsSealed ) {
				il.Emit(OpCodes.Callvirt, method);
			}
			else {
				il.Emit(OpCodes.Call, method);
			}

			if( method.ReturnType == typeof(void) ) {
				il.Emit(OpCodes.Ldnull);
			}
			else if( method.ReturnType.IsValueType ) {
				il.Emit(OpCodes.Box, method.ReturnType);
			}
			il.Emit(OpCodes.Ret);

			return (MethodDelegate)dm.CreateDelegate(typeof(MethodDelegate));
		}

        #region PropertyGetter
        public static GetValueDelegate CreatePropertyGetterDelegate(this PropertyInfo property)
		{
            Shield.AssertNotNull(ref property, "property");
            Shield.AssertIsTrue(property.CanRead, "property.CanRead");

			MethodInfo getMethod = property.GetGetMethod(true);

			DynamicMethod dm = new DynamicMethod("PropertyGetter", typeof(object),
				new Type[] { typeof(object) },
				property.DeclaringType, true);

			ILGenerator il = dm.GetILGenerator();

			if( !getMethod.IsStatic ) {
				il.Emit(OpCodes.Ldarg_0);
				il.EmitCall(OpCodes.Callvirt, getMethod, null);
			}
			else
				il.EmitCall(OpCodes.Call, getMethod, null);

			if( property.PropertyType.IsValueType )
				il.Emit(OpCodes.Box, property.PropertyType);

			il.Emit(OpCodes.Ret);

			return (GetValueDelegate)dm.CreateDelegate(typeof(GetValueDelegate));
		}

        public static TargetGetter CreatePropertyGetter(this PropertyInfo property)
        {
            return new TargetGetter()
            {
                Name = property.Name,
                ValueType = property.PropertyType,
                Getter = CreatePropertyGetterDelegate(property)
            };
        }
        #endregion

        #region PropertySetter
        public static SetValueDelegate CreatePropertySetterDelegate(this PropertyInfo property)
		{
            Shield.AssertNotNull(ref property, "property");
            Shield.AssertIsTrue(property.CanWrite, "property.CanWrite");

			MethodInfo setMethod = property.GetSetMethod(true);

			DynamicMethod dm = new DynamicMethod("PropertySetter", null,
				new Type[] { typeof(object), typeof(object) },
				property.DeclaringType, true);

			ILGenerator il = dm.GetILGenerator();

			if( !setMethod.IsStatic ) {
				il.Emit(OpCodes.Ldarg_0);
			}
			il.Emit(OpCodes.Ldarg_1);

			EmitCastToReference(il, property.PropertyType);
			if( !setMethod.IsStatic && !property.DeclaringType.IsValueType ) {
				il.EmitCall(OpCodes.Callvirt, setMethod, null);
			}
			else
				il.EmitCall(OpCodes.Call, setMethod, null);

			il.Emit(OpCodes.Ret);

			return (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate));
		}

        public static TargetSetter CreatePropertySetter(this PropertyInfo property)
        {
            return new TargetSetter() 
            {
                Name = property.Name,
                Setter = CreatePropertySetterDelegate(property),
                ValueType = property.PropertyType
            };
        }
        #endregion

        #region FieldGetter
        public static GetValueDelegate CreateFieldGetterDelegate(this FieldInfo field)
		{
            Shield.AssertNotNull(ref field, "field");

			DynamicMethod dm = new DynamicMethod("FieldGetter", typeof(object),
				new Type[] { typeof(object) },
				field.DeclaringType, true);

			ILGenerator il = dm.GetILGenerator();

			if( !field.IsStatic ) {
				il.Emit(OpCodes.Ldarg_0);

				EmitCastToReference(il, field.DeclaringType);  //to handle struct object

				il.Emit(OpCodes.Ldfld, field);
			}
			else
				il.Emit(OpCodes.Ldsfld, field);

			if( field.FieldType.IsValueType )
				il.Emit(OpCodes.Box, field.FieldType);

			il.Emit(OpCodes.Ret);

			return (GetValueDelegate)dm.CreateDelegate(typeof(GetValueDelegate));
		}

        public static TargetGetter CreateFieldGetter(this FieldInfo field)
        {
            return new TargetGetter()
            {
                Name = field.Name,
                ValueType = field.FieldType,
                Getter = CreateFieldGetterDelegate(field)
            };
        }
        #endregion

        #region FieldSetter
        public static SetValueDelegate CreateFieldSetterDelegate(this FieldInfo field)
		{
            Shield.AssertNotNull(ref field, "field");

			DynamicMethod dm = new DynamicMethod("FieldSetter", null,
				new Type[] { typeof(object), typeof(object) },
				field.DeclaringType, true);

			ILGenerator il = dm.GetILGenerator();

			if( !field.IsStatic ) {
				il.Emit(OpCodes.Ldarg_0);
			}
			il.Emit(OpCodes.Ldarg_1);

			EmitCastToReference(il, field.FieldType);

			if( !field.IsStatic )
				il.Emit(OpCodes.Stfld, field);
			else
				il.Emit(OpCodes.Stsfld, field);
			il.Emit(OpCodes.Ret);

			return (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate));
		}

        public static TargetSetter CreateFieldSetter(this FieldInfo field)
        {
            return new TargetSetter()
            {
                Name = field.Name,
                Setter = CreateFieldSetterDelegate(field),
                ValueType = field.FieldType
            };
        }
        #endregion

        public static List<TargetGetter> GetGetters(
            Type type,
            Func<PropertyInfo, bool> filterProperty,
            Func<FieldInfo, bool> filterField)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(k => k.CanRead &&
                            (filterProperty == null || !filterProperty(k)))
                .Select(p => p.CreatePropertyGetter());

            var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public)
                         .Where(k => !k.IsInitOnly &&
                                     (filterField == null || !filterField(k)))
                         .Select(p => p.CreateFieldGetter());

            return props.Union(fields).ToList();
        }

        public static List<TargetSetter> GetSetters(
            Type type,
            Func<PropertyInfo, bool> filterProperty,
            Func<FieldInfo, bool> filterField)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                            .Where(k => k.CanWrite &&
                                        (filterProperty == null || !filterProperty(k)))
                            .Select(p => p.CreatePropertySetter());

            var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public)
                         .Where(k => !k.IsInitOnly &&
                                     (filterField == null || !filterField(k)))
                         .Select(p => p.CreateFieldSetter());

            return props.Union(fields).ToList();
        }

        public static List<TargetGetter> GetGetters(Type type)
        {
            return GetGetters(type, null, null);
        }

        private static void EmitCastToReference(ILGenerator il, Type type)
		{
			if( type.IsValueType )
				il.Emit(OpCodes.Unbox_Any, type);
			else
				il.Emit(OpCodes.Castclass, type);
		}
	}
}
